Sound Manager 3.3 Release Notes

Sound Manager 3.3 Release Notes

 

Here is the release notes for the current Sound Manager 3.3. In addition to bug fixes, the most notable new feature for this release is the ability to schedule sounds to be played at a specific time. To further aid in development efforts by our developers, we will be providing a debugging version of the Sound Manager. This debugging version will have asserts in the code which will report any errors during execution. The idea is that this will help developers locate bugs in their code quickly and easily.

 


New Features

General


Scheduling Sounds

A major new feature of Sound Manager 3.3 is the ability to schedule a sound. The mixer supports scheduling sounds in the past and future. If the start time is in the past, then the mixer will fast forward through the source at non-interrupt time attempting to catch up with the current time. Once there, the sounds included into the mix. If the sound is in the future or the past, the mixer will begin mixing the source into the output stream at the specified time within a sample of accuracy.

There are two new sound commands for supporing the sound clock; clockComponentCmd and getClockComponentCmd. These commands are used by QuickTime to obtain the sound clock which is used as the main time base. The Sound Manager sound clock is only available when QuickTime is installed. This allows QuickTime's video to be synchronized with the audio stream, and can be used to synchronize multiple audio sources as well. There is one sound clock per source (e.g. sound channel) which is updated with each hardware buffer. The sound clock counts samples being consumed by the hardware deivce as it is pass from the Mixer.

Once the clock ComponentInstance has been obtained, any and all of the QuickTime clock component calls (e.g. ClockGetTime) can be used. Below is an example of scheduling two sounds with the sound clock. It assumes that the SoundHeader of the ScheduledSoundHeader struct has already been established. Note that although each channel has it's own clock, both are synchronized with the mixer. Therefore obtaining the current time from one channel will result in the same time for the second. This allows both channels to use the same start time.

Note that a sound channel can only have one scheduled sound at any given time. The Mixer does not queue up additional scheduled sounds. You can stream a sound continuously by starting the first buffer with the scheduledSoundCmd, then use the bufferCmd and callBackCmd sequence with SndDoCommand() to stream additional samples. This has been the techniqued used in the past. By replacing the first bufferCmd with the new scheduledSoundCmd you can cause the samples to start playing at a specific point in time.


Multiplatform support

The Sound Manager sources are now multiplatform. They are built for the Mac OS, Windows 95, Windows NT, and soon could be for Unix platforms. This means the same Sound Manager is available for each of these platforms, and contains the same API and features. This brings into being the issue of "endianness". Basically, endian conversion is treated exactly as a compression conversion. Any non-native endian format will be required to be "decompressed" into the native format. All audio decompressors (e.g. MACE, IMA, uLaw, aLaw) must decompress into the native format of the platform. There are three distinct groups to be considered here; 1. a program playing audio, 2. a sound component manipulating the audio, and 3. the output device sending the audio to a hardware device.

For applications playing audio for any non-native endian formats (e.g. 16 bit Little Endian on the Macintosh) use the CmpSoundHeader, SndDoubleBufferHeader2, or SoundComponentData with the format set to k16BitLittleEndianFormat. This causes the Sound Manager to install the 16 Bit Little Endian codec into the sound channel. (Note that the SoundHeader, ExtSoundHeader, and SndDoubleBufferHeader struct do not have a format field.)

For sound component developers, there are two cases to consider; the compressor and decompressor components. The uncompressed formats are assumed to be in native format of the platform. In other words, the input to a compressor is in native format and all output from a decompressor is in native format.

For sound output devices, the mixer is generating audio in the platform's native format. If the hardware requires non-native format, it's the responsibility of the output device to convert to the native format.


Sound Formats

All of the non-compressed formats supported by Apple will also support both big and little endian format. The Apple sound components will assume big endian format, unless otherwise configured using the siDecompressionParams or siCompressionParams selector. Decompression components should always output native format, and default to big endian for their input. Compression components should default to big endian format for output, and consume native endian for input.

 

a-law compression

A new sound codec has been included with the standard set of built-in compression formats, the aLaw compression. The uLaw format is used in North America and Japan, while the aLaw format is used in Europe and the rest of the world.
 

24 and 32 Bit Integer

Two new sample sizes are supported, integer 24 and 32 bit, and named k24BitFormat and k32BitFormat respectivly. For this release, these samples are converted into 8 or 16 bit depending on the hardware. Sample rate conversion and mixing at this bit resolution will be provided in a future release to support 24 and 32 bit hardware.

 

32 and 64 Bit Floating Point

Two new codecs have been added to import and export samples. These are in 32 bit and 64 bit floating point formats, and named kFloat32Format and kFloat64Format respectively. This allows more accurate conversion to other sample sizes or the application of effects (e.g. 3D sound localization). These two floating point codecs also support a new selector siSlopeAndIntercept which points to a SoundSlopeAndInterceptRecord struct which can control the conversion process of floating point samples into integer samples. This struct is included in the Sound.h interface file and shown below.

 

struct SoundSlopeAndInterceptRecord {
Float64 slope;
Float64 intercept;
Float64 minClip;
Float64 maxClip;
};


New Functions

 

SoundConverterSetInfo() and SoundConverterGetInfo()

Two new function calls have been added to to the SoundConverter suite, SoundConverterGetInfo() and SoundConverterSetInfo(). This allows any of the sound info selectors and their parameters to be used when calling the SoundConverter. An example for converting to 32 bit little endian data is given in the sample code section.


siDecompressionParams and siCompressionParams

Two new sound info selectors have been created, siDecompressionParams and siCompressionParams. These selectors are used to pass an atom list to the sound component. Atoms are always in big endian format. The content of the new audio atom list can consist of many atoms, and always ends with the AudioTerminatorAtom. The first atom in the list should be the AudioFormatAtom which specifies which sound component is responsible for the atoms contained within the list. Calling SndGetInfo() with these two selectors will return a handle to an atom list. This handle is the responsibility of the caller to be disposed of. The SndSetInfo() function requres a pointer to an atom list, and is the responsiblity of the caller to be disposed of. For example, to change a decompression sound component from its default of big endian to little endian input use the AudioEndianAtom with the "littleEndian" field set to true. Below is an example of changing the 32 bit floating point codec to consume little endian format. Refer to the sample code section for additional code.
void *atoms;
err = CreateAudioAtomsList(&atoms, k32BitFormat, false);
if (err != noErr) return (err);

err = SndSetInfo(chan, siDecompressionParams, atoms);

 


siOptionsDialog

Certain sound compression components can show a dialog of options. To determine if the codec supports a dialog, use the SoundComponentGetInfo() function with the siOptionsDialog selector. It will return a 16 bit value, with any non-zero value meaning true. If you use the SoundComponentSetInfo() function the codec will show the dialog and change it's settings accordingly. For example, the two floating point and the two new 24 and 32 bit integer codecs can show a dialog that allows the user to specify big or little endian. You can also change a compressor's setting using the siCompressionParams selector.

 

short hasDialog;

err = SoundComponentGetInfo(compressor, nil, siOptionsDialog, &hasDialog);
if (err != noErr) return (err);
if (hasDialog)
{
err = SoundComponentSetInfo(compressor, nil, siOptionsDialog, nil);
if (err != noErr) return (err);
}


Sound Commands

The new sound commands that have been added include: clockComponentCmd, getClockComponentCmd, scheduledSoundCmd, and linkSoundComponentsCmd. These sound commands are used with the SndDoImmediate() and SndDoCommand() functions.

clockComponentCmd

This command turns the sound clock on or off. Use this command to turn on the sound clock before attempting to use the getClockComponentCmd. Set the value of param1 in this command to be true or false, where true turns on the clock.


getClockComponentCmd

This command returns the the sound clock component instance. Set the value of param2 to be a pointer to a ComponentInstance.


scheduledSoundCmd

Use this command with the new ScheduledSoundHeader struct. It's similar to the bufferCmd in that param2 contains a pointer to this struct. This command can be used to schedule a sound and/or to install a callBackCmd with this one command. The first field of this stuct is a union of one of the three existing SoundHeader types, as used in the bufferCmd. The flags field is used to specify if there is a valid TimeRecord and/or parameters for the CallBackProc.

 

/* ScheduledSoundHeader flags*/
enum {
kScheduledSoundDoScheduled = 1 << 0,
kScheduledSoundDoCallBack = 1 << 1
};

 

struct ScheduledSoundHeader {
SoundHeaderUnion u;
long flags;
short reserved;
short callBackParam1;
long callBackParam2;
TimeRecord startTime;
};

 


linkSoundComponentsCmd

Use this command to configure the given sound channel's components to support your sound format. By default a channel will be configured to play 8 bit non-compressed samples. If you wanted to play 16 bit or compressed audio, then you had to start your sound at non-interrupt time so that the proper sound components would be opened and installed. By using this new command you can point to a SoundComponentData struct in param2, and the proper set of sound components will be configured into your channel. At this point you can start a sound playing at interrupt time.


Bug Fixes


Interface Changes

 

SoundInput.h and SoundComponents.h have been merged into Sound.h. The former two interface files are now empty, but simply include the later.

 

Previously defined in SoundComponents.h were a set of component sub-types which were used in the 'thng' resource of an audio codec. This OSType was matched with a given sound's format, which determined the codec which was necessary to support the given sound. Unfortunately, the usage of these constants were not obvious to many and are required as the value set in the "format" parameter of the functions GetCompressionInfo(), GetCompressionName (), SetupSndHeader (), and SetupAIFFHeader (), and in the CmpSoundHeader, SndDoubleBufferHeader2, CompressionInfo and SoundComponentData stucts. A new set of constants have been created to replace these with the addition of the recently supported formats.

 

kSoundNotCompressed
k8BitOffsetBinaryFormat (was kOffsetBinary)
kMACE3Compression (was kMace3SubType)
kMACE6Compression (was kMace6SubType)
kIMACompression (was kIMA4SubType)
kULawCompression (was kULawSubType)
kALawCompression
kFloat32Format
kFloat64Format
k24BitFormat
k32BitFormat
k16BitBigEndianFormat (was kTwosComplement)
k16BitLittleEndianFormat (was kLittleEndianSubType)
kMicrosoftADPCMFormat
kDVIIntelIMAFormat
kDVAudioFormat


Sample Code

 

Determining the Sound Manager version

 

The Sound Manager provides a routine, SndSoundManagerVersion(), which returns the currently installed version. Many developers have been confused as to how to determine which Sound Manager is installed. Below is the simpliest method to check for Sound Manager 3.1 or later, use the following code. Note that the NumVersionVariant struct is being used from the lastest version of the MacTypes interface file. This is a union of the old NumVersion struct with its various parts, and a 32 bit long value which allows for easy comparisons.

 

Boolean HasSoundManager3_1(void)
{
NumVersionVariant version;
 
version.parts = SndSoundManagerVersion();
return (version.whole >= 0x03100000) // version 3.1
}


Converting to little endian

OSErr ConvertToLittleEndian(Ptr inputPtr, Ptr outputPtr)
{
SoundConverter sc;
SoundComponentData inputFormat, outputFormat;
unsigned long inputFrames, inputBytes;
unsigned long outputFrames, outputBytes;
void *littleEndianAtomsList;
OSErr err;
 
inputFormat.flags = 0;
inputFormat.format = k16BitBigEndianFormat;
inputFormat.numChannels = 1;
inputFormat.sampleSize = 16;
inputFormat.sampleRate = rate22050hz;
inputFormat.sampleCount = 0;
inputFormat.buffer = nil;
inputFormat.reserved = 0;
 
outputFormat.flags = 0;
outputFormat.format = k32BitFormat;
outputFormat.numChannels = 1;
outputFormat.sampleSize = 16;
outputFormat.sampleRate = rate22050hz;
outputFormat.sampleCount = 0;
outputFormat.buffer = nil;
outputFormat.reserved = 0;
 
err = SoundConverterOpen(&inputFormat, &outputFormat, &sc);
if (err != noErr) return (err);
 
err = SoundConverterGetBufferSizes(sc, kOurInputBytesTarget, &inputFrames,
&inputBytes, &outputBytes);
if (err != noErr) return (err);
 
err = CreateAudioAtomsList(k32BitFormat, true, &littleEndianAtomsList);
if (err != noErr) return (err);
 
err = SoundConverterSetInfo(sc, siCompressionParams, littleEndianAtomsList);
if (err != noErr) return (err);
 
err = SoundConverterBeginConversion(sc);
if (err != noErr) return (err);
 
err = SoundConverterConvertBuffer(sc, inputPtr, inputFrames, outputPtr,
&outputFrames, &outputBytes);
if (err != noErr) return (err);
 
err = SoundConverterEndConversion(sc, outputPtr, &outputFrames, &outputBytes);
if (err != noErr) return (err);
 
err = SoundConverterClose(sc);
if (err != noErr) return (err);
}


Audio atoms

Atoms are aways in big endian format. The atoms contained in this list can be in any order ending with the AudioTerminatorAtom. No assumptions should be made about reading an audio atom list, other than the last atom is the AudioTerminatorAtom. Using SndGetInfo() and the siDecompressionParams selector will return a list of atoms of any possible ordering.

 

OSErr CreateAudioAtomsList(OSType format, Boolean littleEndian,
void **littleEndianAtomsList)
{
typedef struct {
AudioFormatAtom formatData;
AudioEndianAtom endianData;
AudioTerminatorAtom terminatorData;
} AudioDecompressionAtoms, *AudioDecompressionAtomsPtr;
 
atoms = (AudioDecompressionAtomsPtr)NewPtr(sizeof(AudioDecompressionAtoms));
if (atoms == nil)
return (MemError());
 
atoms->formatData.size = EndianU32_NtoB(sizeof(AudioFormatAtom));
atoms->formatData.atomType = EndianU32_NtoB(kAudioFormatAtomType);
atoms->formatData.format = EndianU32_NtoB(format);
 
atoms->endianData.size = EndianU32_NtoB(sizeof(AudioEndianAtom));
atoms->endianData.atomType = EndianU32_NtoB(kAudioEndianAtomType);
atoms->endianData.littleEndian = EndianU16_NtoB(littleEndian);
 
atoms->terminatorData.size = EndianU32_NtoB(sizeof(AudioTerminatorAtom));
atoms->terminatorData.atomType = EndianU32_NtoB(kAudioTerminatorAtomType);
 
*littleEndianAtomsList = atoms;
return (noErr);
}
 
 
Boolean GetFormatAndEndianFromAtomsList(UserDataAtom *atom, OSType *format,
Boolean *littleEndian)
{
Boolean moreAtoms;
 
moreAtoms = true;
do
{
if (EndianS32_BtoN(atom->size) < 8)
return (false); // bad atom size
switch (EndianU32_BtoN(atom->atomType))
{
case kAudioFormatAtomType:
*format = EndianU32_BtoN(((AudioFormatAtom *)atom)->format);
break;
case kAudioEndianAtomType:
*littleEndian = EndianU16_BtoN(((AudioEndianAtom *)atom)->littleEndian);
break;
 
case kAudioTerminatorAtomType:
moreAtoms = false;
break;
 
default: // unknown atom type
break;
}
atom = (UserDataAtom *)((long)atom + EndianS32_BtoN(atom->size));
} while (moreAtoms);
}


Using the Sound Clock

 

// turn on the sound clock for two channels
 
cmd.cmd = clockComponentCmd;
cmd.param1 = true;
err = SndDoImmediate(gChan1, &cmd);
if (err != noErr) return (err);
 
err = SndDoImmediate(gChan2, &cmd);
if (err != noErr) return (err);
 
// get the sound clock component from one channel
 
cmd.cmd = getClockComponentCmd;
cmd.param2 = (long)&clock;
err = SndDoImmediate(gChan1, &cmd);
if (err != noErr) return (err);
 
// get the current time from one channel
 
scheduledSound1.startTime.base = nil;
err = ClockGetTime(clock, &scheduledSound1.startTime);
if (err != noErr) return (err);
 
// add 1/2 second to the current time
 
deltaTime.value.hi = 0;
deltaTime.value.lo = 1;
deltaTime.scale = 2;
AddTime(&scheduledSound1.startTime, &deltaTime);
 
// schedule two sounds to play at the same starting time
 
scheduledSound1.flags = kScheduledSoundDoScheduled;
scheduledSound2.flags = kScheduledSoundDoScheduled;
scheduledSound2.startTime = scheduledSound1.startTime;
 
cmd.cmd = scheduledSoundCmd;
cmd.param2 = (long)&scheduledSound1;
err = SndDoImmediate(gChan1, &cmd);
if (err != noErr) return (err);
 
cmd.param2 = (long)&scheduledSound2;
err = SndDoImmediate(gChan2, &cmd);
if (err != noErr) return (err);
 

© 1997 Apple Computer, Inc.